/**
 * @author Steve Sedlmayr
 * @mail ssedlmayr@gmail.com
 * @link http://code.google.com/p/chitin/
 * @version 0.1
 
	The MIT License

	Copyright (c) 2009 Steve Sedlmayr

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/

package srs.chitin.util.implementation.vo 
{
	import srs.chitin.mvstate.base.ModelBase;
	
	import flash.events.Event;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	/**
	 * Implementation layer utility class for dealing with module 
	 * and mvstate package value objects.
	 * */
	public class VOUtil {
		public function VOUtil() {}
		
		/** 
		 * A private method invoked by cloning operations that instantiates, initializes 
		 * and returns an object with a sourceVO object, targetVO object and modelEnumeration array.
		 * 
		 * @param sourceVO A class definition or instance of a source VO for a cloning operation.
		 * 
		 * @param targetVO A class definition or instance of a target VO for a cloning operation.
		 * 
		 * @param sourceIsInstance (OPTIONAL) An optional flag indicating whether the sourceVO value is 
		 * a class definition or an instance.
		 * 
		 * @param targetIsInstance (OPTIONAL) An optional flag indicating whether the targetVO value is 
		 * a class definition or an instance.
		 * 
		 * @returns An object with a sourceVO object, targetVO object and modelEnumeration array.
		 * 
		 * @private 
		 */
		private static function initializeValueObjects(sourceVO:*, targetVO:*, sourceIsInstance:Boolean=false, targetIsInstance:Boolean=false) : Object {
			var sourceDefinition:Object = getDefinitionByName(getQualifiedClassName(sourceVO));
			var targetDefinition:Object = getDefinitionByName(getQualifiedClassName(targetVO));
			var _sourceVO:* = ( sourceIsInstance == true ) ? sourceVO : new sourceDefinition();
			var _targetVO:* = ( targetIsInstance == true ) ? targetVO : new targetDefinition();
			var modelEnumeration:Array = _sourceVO["getEnumeration"]();
			
			if ( modelEnumeration.length < 1 && sourceIsInstance == false ) {
				for ( var property:String in sourceVO ) {
					if ( property.indexOf("_") != -1 ) {
						modelEnumeration.push(property.substring(1));
					}
				}
			}
			
			return {_sourceVO:_sourceVO, _targetVO:_targetVO, modelEnumeration:modelEnumeration};
		}
		
		/** 
		 * Clones a VO by copying the fields from one VO to another.
		 * 
		 * @param sourceVO A class definition or instance of a source VO for a cloning operation.
		 * 
		 * @param targetVO A class definition or instance of a target VO for a cloning operation.
		 * 
		 * @param sourceIsInstance (OPTIONAL) An optional flag indicating whether the sourceVO value is 
		 * a class definition or an instance.
		 * 
		 * @param targetIsInstance (OPTIONAL) An optional flag indicating whether the targetVO value is 
		 * a class definition or an instance.
		 * 
		 * @param changed (OPTIONAL) An Array of String values of field names for clone filtration. If
		 * defined, only items in this Array and those that do not already exist on the target will be 
		 * cloned; otherwise, all fields of the source are cloned to the target.
		 * 
		 * @returns A cloned VO.
		 */
		public static function clone(sourceVO:*, targetVO:*, sourceIsInstance:Boolean=false, targetIsInstance:Boolean=false, changed:Array=null) : * {
			//Initialize the sourcVO and targetVO and get the sourcVO's field enumeration.
			var initializationObject:Object = initializeValueObjects(sourceVO, targetVO, sourceIsInstance, targetIsInstance);
			
			//Iterate over the sourceVO's field enumeration.
			for ( var i:Number = 0; i < initializationObject.modelEnumeration.length; i++ ) {
				var propertyName:String = initializationObject.modelEnumeration[i];
				var propertyValue:* = initializationObject._sourceVO["getPropertyByName"](propertyName);
				
				/* If the field is not defined on the target already, or 
				   the changed filter is not defined, clone the field. */
				if ( initializationObject._targetVO["getPropertyByName"](propertyName) == null || changed == null ) {
					initializationObject._targetVO["addEnumerationItem"](propertyName, propertyValue);
					initializationObject._targetVO["setPropertyByName"](propertyName, propertyValue);
				} else { //Otherwise, clone only those fields defined in the filter. Note that fields undefined on the target outside the filter will still get cloned in this case.
					for ( var j:Number = 0; j < changed.length; j++ ) {
						if ( propertyName == changed[j] ) {
							initializationObject._targetVO["addEnumerationItem"](propertyName, propertyValue);
							initializationObject._targetVO["setPropertyByName"](propertyName, propertyValue);
							
							break;
						}
					}
				}
			}
			
			return initializationObject._targetVO;
		}
		
		/** 
		 * Clones a VO by copying the fields from one VO to another based purely on the presence or 
		 * not of the <code>changed</code> filter. Unlike the <code>clone()</code> method, 
		 * <code>cloneToEnumeration()</code> either clones everything from the source, or only those 
		 * fields defined in the <code>changed</code> filter, and only clones fields that exist in 
		 * both value objects.
		 * 
		 * @param sourceVO A class definition or instance of a source VO for a cloning operation.
		 * 
		 * @param targetVO A class definition or instance of a target VO for a cloning operation.
		 * 
		 * @param sourceIsInstance (OPTIONAL) An optional flag indicating whether the sourceVO value is 
		 * a class definition or an instance.
		 * 
		 * @param targetIsInstance (OPTIONAL) An optional flag indicating whether the targetVO value is 
		 * a class definition or an instance.
		 * 
		 * @param changed (OPTIONAL) An Array of String values of field names for clone filtration. If
		 * defined, only items in this Array and those that do not already exist on the target will be 
		 * cloned; otherwise, all fields of the source are cloned to the target.
		 * 
		 * @returns A cloned VO.
		 */
		public static function cloneToEnumeration(sourceVO:*, targetVO:*, sourceIsInstance:Boolean=false, targetIsInstance:Boolean=false, changed:Array=null) : * {
			//Initialize the sourcVO and targetVO and get the sourcVO's field enumeration.
			var initializationObject:Object = initializeValueObjects(sourceVO, targetVO, sourceIsInstance, targetIsInstance);
			var targetEnumeration:Array = initializationObject._targetVO["getEnumeration"]();
			
			//Iterate over the sourceVO's field enumeration to extract its properties and values.
			for ( var i:Number = 0; i < initializationObject.modelEnumeration.length; i++ ) {
				var propertyName:String = initializationObject.modelEnumeration[i];
				var propertyValue:* = initializationObject._sourceVO["getPropertyByName"](propertyName);
				
				//Iterate over the sourceVO's field enumeration again to see if the targetVO's field enumeration also contains the field.
				for ( var k:Number = 0; k < initializationObject.modelEnumeration.length; k++ ) {
					/* Only clone fields that exist in both enumerations. Comparing 
					   enumerations instead of checking for the property on the class 
					   enforces the contract that the field must be enumerated instead 
					   of simply being defined directly. */
					if ( propertyName == targetEnumeration[k] ) {
						if ( changed == null ) { //The filter is not defined, so clone all matches.
							initializationObject._targetVO["setPropertyByName"](propertyName, propertyValue);
						} else { //Otherwise, only clone matches that are also defined in the filter.
							for ( var j:Number = 0; j < changed.length; j++ ) {
								if ( propertyName == changed[j] ) {
									initializationObject._targetVO["setPropertyByName"](propertyName, propertyValue);
									
									break;
								}
							}
						}
						
						break;
					}
				}
			}
			
			return initializationObject._targetVO;
		}
		
		/** 
		 * Injects a target model with value updates to properties on the model's VO via a 
		 * parameterized event.
		 * 
		 * @param target The model to inject with the new values.
		 * 
		 * @param event An event to inject into the model.
		 * 
		 * @param targetVO The VO the model utilizes for its data fields.
		 * 
		 * @param properties The names of the properties you wish to update. These must be defined 
		 * in the VO's enumeration.
		 *  
		 * @param values The values you wish to update the properties with. The indices of the properties 
		 * and the values match.
		 * 
		 * @param forcedChangeNotifications (OPTIONAL) If you wish the model to notify its observing 
		 * views of the values of certain fields even if they have not changed, defined them in this 
		 * parameter in addition to the properties Array parameter. 
		 */
		public static function inject(target:ModelBase, event:Event, targetVO:*, properties:Array, values:Array, forcedChangeNotifications:Array=null) : void {
			event["inboundNotificationVO"] = new targetVO();
			
			if ( forcedChangeNotifications != null ) { event["forcedChangeNotifications"] = forcedChangeNotifications; }
				
			for ( var i:Number = 0; i < properties.length; i++ ) {
				event["inboundNotificationVO"]["setPropertyByName"](properties[i], values[i]);
			}
			
			if ( target != null ) { target.dispatchEvent(event); }
		}
		
		/** 
		 * Gets a property from a VO.
		 * 
		 * @param sourceVO The VO class from which to retrieve the value.
		 * 
		 * @param parameterName The name of the field.
		 * 
		 * @returns The value of the property.  
		 */
		public static function getProperty(sourceVO:*, parameterName:String) : * {
			var _sourceVO:* = new sourceVO();
				
			return _sourceVO["getPropertyByName"](parameterName);
		}
		
		/** 
		 * Sets a property on a VO.
		 * 
		 * @param targetVO The VO class on which to set the value.
		 * 
		 * @param parameterName The name of the field.
		 * 
		 * @param value The desired value of the field.
		 */
		public static function setProperty(targetVO:*, parameterName:String, value:*) : void {
			var _targetVO:* = new targetVO();
				_targetVO["setPropertyByName"](parameterName, value);
		}
	}
}